package dao;

import util.JDBCUtil;

import java.lang.reflect.Field;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

public class BaseDao {
    // 公共的查询方法  返回的是单个对象
    /**
     * 泛型查找，执行SQL查询并将结果映射为指定类型T ,<T> 泛型声明
     * @param clazz 要映射的目标类的字节码对象：用于确定返回对象的类型；反射创建对象，获取类的元信息
     *              传入user.class就是返回user
     * @param sql
     * @param args 用于传递占位符对应值 ?
     * @return
     * @param <T>
     */
    public <T> T baseQueryObject(Class<T> clazz, String sql, Object ... args) {
        T t = null;
        Connection connection = JDBCUtil.getConnection();
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        int rows = 0;
        try {
            // 准备语句对象
            preparedStatement = connection.prepareStatement(sql);
            // 设置语句上的参数
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i + 1, args[i]);
                //为什么是 i+1?? args[i]??
                /**
                 * JDBC 占位符索引从1开始
                 */
            }

            // 执行 查询
            resultSet = preparedStatement.executeQuery();
            if (resultSet.next()) {
                t = (T) resultSet.getObject(1);
                //为什么是1？？
                /**
                 * 获取当前行的第一列数据
                 */
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            /**
             * 释放逻辑：先判断是否为空指针，对每个资源调用关闭释放
             * 释放顺序：先创建的后释放
             * resultset依赖于预编译语句
             * 预编译语句依赖于数据库连接
             */
            if (null != resultSet) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (null != preparedStatement) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }

            }
            JDBCUtil.releaseConnection();
        }
        return t;
    }
    // 公共的查询方法  返回的是对象的集合

    public <T> List<T> baseQuery(Class clazz, String sql, Object ... args){
        List<T> list =new ArrayList<>();
        Connection connection = JDBCUtil.getConnection();
        PreparedStatement preparedStatement=null;
        ResultSet resultSet =null;
        int rows = 0;
        try {
            // 准备语句对象
            preparedStatement = connection.prepareStatement(sql);
            // 设置语句上的参数
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i+1,args[i]);
            }
            // 执行 查询
            resultSet = preparedStatement.executeQuery();
            ResultSetMetaData metaData = resultSet.getMetaData();
            /**
             * resultmetasata 用于描述结果集的数据信息，列数，列名称，数据类型 长度
             */
            int columnCount = metaData.getColumnCount();
            // 将结果集通过反射封装成实体类对象
            while (resultSet.next()) {
                // 使用反射实例化对象
                Object obj =clazz.getDeclaredConstructor().newInstance();
/**
 * 创建目标类的实例对象，动态创建，无需提前获知具体类名，代码适配所有类
 */
                for (int i = 1; i <= columnCount; i++) {
                    String columnName = metaData.getColumnLabel(i);
                    /**
                     * 获取第i列的列标签，数据库列名 meta是结果集元数据，下面则是获取第i列的实际值
                     */
                    Object value = resultSet.getObject(columnName);
                    // 处理datetime类型字段和java.util.Data转换问题
                    if(value.getClass().equals(LocalDateTime.class)){
                        value= Timestamp.valueOf((LocalDateTime) value);
                        /**
                         * 当数据库字段是 DATETIME 或 TIMESTAMP 类型时，JDBC 可能返回 LocalDateTime 类型，
                         * 而实体类字段可能需要 Timestamp 类型，因此需要显式转换
                         */
                    }
                    Field field = clazz.getDeclaredField(columnName);
                    /**
                     * 通过反射获取实体类和与数据库列名对应的字段 Field对象
                     * getDeclaredField获取类中任意访问权限字段
                     * 要求数据库列名与实体类字段名一致（或通过别名适配
                     */
                    field.setAccessible(true);
                    field.set(obj,value);
                    /**
                     * 将数据库列的值设置到实体对象的对应字段，通过field对象set方法动态
                     * 为对象赋值，无需setter方法
                     */
                }

                list.add((T)obj);
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null !=resultSet) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            if (null != preparedStatement) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
            JDBCUtil.releaseConnection();
        }
        return list;
    }
    // 通用的增删改方法
    public int baseUpdate(String sql,Object ... args) {
        // 获取连接
        Connection connection = JDBCUtil.getConnection();
        PreparedStatement preparedStatement=null;
        int rows = 0;
        try {
            // 准备语句对象
            preparedStatement = connection.prepareStatement(sql);
            // 设置语句上的参数
            for (int i = 0; i < args.length; i++) {
                preparedStatement.setObject(i+1,args[i]);
            }

            // 执行 增删改 executeUpdate
            rows = preparedStatement.executeUpdate();
            // 释放资源(可选)


        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (null != preparedStatement) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }

            }
            JDBCUtil.releaseConnection();
        }
        // 返回的是影响数据库记录数
        return rows;
    }
}